﻿package fab.geom
{
	import flash.geom.Point;
	import flash.geom.Rectangle;

	import flash.display.GraphicsPathWinding;
	import flash.display.Graphics;
	
	public class Bezier
	{
		public var dots:Vector.<Point>;
		private var _length:Number;
		static public var _factoriel:Array = new Array();
		static public var _bernstein_poly_coeff:Array = new Array();
		/**
		 * Bezier curve
		 * 
		 * @param A Vector of points values that represent the starting point, n control points, a ending point
		 * 
		 * @return A Bezier curve
		 */
		public function Bezier( arr:Vector.<Point> )
		{
			dots = arr;
		}
		public function pointAt( t:Number ):Point
		{
			var x:Number = 0;
			var y:Number = 0;
			var l:int = dots.length;
			for (var i:int = 0; i < l; i++) 
			{
				var c:Number = Math.pow( t, i ) * Math.pow( 1 - t, l - 1 - i ) * bernstein_poly_coeff( l - 1, i );
				x += c * dots[ i ].x;
				y += c * dots[ i ].y;
			}
			return new Point( x, y );
		}	
		// n est le degré de la courbe de Bezier (n=2 : cubic bezier....... pourraientt etre précalculés !!!
		public function bernstein_poly_coeff( n:uint, i:uint ):Number//, t:Number ):Number
		{
			if ( _bernstein_poly_coeff[ n + "|" + i ] ) return _bernstein_poly_coeff[ n + "|" + i ];
			return _bernstein_poly_coeff[ n + "|" + i ] = factoriel( n ) / ( factoriel( i ) * factoriel( n - i ) );//Math.pow( t, i ) * Math.pow( 1 - t, n - i ) * 
		}
		public function factoriel( n:uint ):uint 
		{
			if ( _factoriel[ n ] ) return _factoriel[ n ];
			if ( n <= 1 ) return _factoriel[ n ] = 1; else return _factoriel[ n ] = n * factoriel(n - 1);
		}
		public function draw( graphics:Graphics, precision:int, couleur:int=0, from:Number=0, to:Number=1 ):void 
		{
			var _commands:Vector.<int> = new Vector.<int>();
			_commands.push( 1 );// _commands.push( 1, 3 );...suffit pour tous les autres !
			var _coord:Vector.<Number> = new Vector.<Number>();
			for ( var t:int = from * 100; t <= to * 100; t += 100 / precision )
			{
				var p:Point = pointAt( t/100 );
				_commands.push( 2 );
				_coord.push( p.x, p.y );
				//Drawer.drawPoint( graphics, p, 0xAA3366 );
			}
			_commands.pop();
			if ( couleur != 0 ) graphics.lineStyle( 0, couleur );
			graphics.drawPath( _commands, _coord, GraphicsPathWinding.NON_ZERO );	
		}
		// precision 100 : 1 seul point, precision 1 : 100 points
		public function get_dots( precision:int, from:Number=0, to:Number=1 ):Vector.<Point>
		{
			var _coord:Vector.<Point> = new Vector.<Point>();
			for ( var t:int = from * 100; t <= to * 100; t += 100 / precision ) _coord.push( pointAt( t/100 ) );
			return _coord;	
		}				

		// naive computation of arc length by summing small segment lengths http://www.algorithmist.net/bezierarclength/srcview/index.html
		public function arcLengthBySegments():Number
		{
			if ( _length != 0 ) return _length;
			var length:Number    = 0;
			var tPrevious:Number = 0;
			var p:Point          = pointAt(0);
			var prevX:Number     = p.x;
			var prevY:Number     = p.y;
			for( var t:Number=0.005; t<=1.0; t+=0.005 )
			{
			p                 = pointAt(t);
			var deltaX:Number = p.x - prevX;
			var deltaY:Number = p.y - prevY;
			length           += Math.sqrt(deltaX*deltaX + deltaY*deltaY);

			prevX = p.x;
			prevY = p.y;
			}

			// exercise:  due to roundoff, it's possible to miss a small segment at the end.  how to compensate??
			return _length = length;
		}
	}
}